local prefs = {}

--------------------------------------------------------------------------
--[[ 通用函数 ]]
--------------------------------------------------------------------------

local oppositeBuffs = {
    buff_hungerretarder = { buff_oilflow = true },
    buff_oilflow = { buff_hungerretarder = true }
}

local function CheckOppositeBuff(buff, target) -- 去除相克的buff，不让某些buff同时存在
    local nobuffs = oppositeBuffs[buff.prefab]
    if nobuffs == nil then
        return
    end
    local abuff
    for k, _ in pairs(nobuffs) do
        abuff = target:GetDebuff(k)
        if abuff ~= nil then
            if abuff.oppositefn_l ~= nil then
                abuff.oppositefn_l(abuff, buff, target)
            end
            target:RemoveDebuff(k)
        end
    end
end

local function StartTimer_attach(buff, target, timekey, timedefault)
    -- 因为是新加buff，不需要考虑buff时间问题
    if timekey == nil or target[timekey] == nil then
        if not buff.components.timer:TimerExists("buffover") then -- 因为onsave比这里先加载，所以不能替换先加载的
            buff.components.timer:StartTimer("buffover", timedefault)
        end
    else
        if not buff.components.timer:TimerExists("buffover") then
            local times = target[timekey]
            if times.add ~= nil then
                times = times.add
            elseif times.replace ~= nil then
                times = times.replace
            elseif times.replace_min ~= nil then
                times = times.replace_min
            else
                buff:DoTaskInTime(0, function()
                    buff.components.debuff:Stop()
                end)
                target[timekey] = nil
                return
            end
            buff.components.timer:StartTimer("buffover", times)
        end
        target[timekey] = nil
    end
end
local function StartTimer_extend(buff, target, timekey, timedefault)
    -- 因为是续加buff，需要考虑buff时间的更新方式
    if timekey == nil or target[timekey] == nil then
        buff.components.timer:StopTimer("buffover")
        buff.components.timer:StartTimer("buffover", timedefault)
    else
        local times = target[timekey]
        target[timekey] = nil
        if times.add ~= nil then -- 增加型：在已有时间上增加，可设置最大时间限制
            local timeleft = buff.components.timer:GetTimeLeft("buffover") or 0
            timeleft = timeleft + times.add

            if times.max ~= nil and timeleft > times.max then
                timeleft = times.max
            end
            buff.components.timer:StopTimer("buffover")
            buff.components.timer:StartTimer("buffover", timeleft)
        elseif times.replace ~= nil then -- 替换型：不管已有时间，直接设置
            buff.components.timer:StopTimer("buffover")
            buff.components.timer:StartTimer("buffover", times.replace)
        elseif times.replace_min ~= nil then -- 最小替换型：若已有时间<该时间时才设置新时间（比较建议的类型）
            local timeleft = buff.components.timer:GetTimeLeft("buffover") or 0
            if timeleft < times.replace_min then
                buff.components.timer:StopTimer("buffover")
                buff.components.timer:StartTimer("buffover", times.replace_min)
            end
        end
    end
end

local function Attached_timer(inst, target, ...)
    inst.entity:SetParent(target.entity)
    inst.Transform:SetPosition(0, 0, 0) -- in case of loading
    inst:ListenForEvent("death", function(owner, data)
        inst.components.debuff:Stop()
    end, target)

    local data = inst._dd
    StartTimer_attach(inst, target, data.time_key, data.time_default)
    if data.fn_start ~= nil then
        data.fn_start(inst, target, ...)
    end
end
local function Detached_timer(inst, target, ...)
    if inst._dd.fn_end ~= nil then
        inst._dd.fn_end(inst, target, ...)
    end
    -- 改成延迟一帧移除，提高兼容性
    inst:DoTaskInTime(0, inst.Remove)
end
local function Extended_timer(inst, target, ...)
    local data = inst._dd
    StartTimer_extend(inst, target, data.time_key, data.time_default)
    if data.fn_again ~= nil then
        data.fn_again(inst, target, ...)
    end
end
local function OnTimerDone(inst, data)
    if data.name == "buffover" then
        inst.components.debuff:Stop()
        return
    end
    if inst._dd.fn_timerdone ~= nil then
        inst._dd.fn_timerdone(inst, data)
    end
end
local function InitTimerBuff(inst)
    inst.components.debuff:SetAttachedFn(Attached_timer)
    inst.components.debuff:SetDetachedFn(Detached_timer)
    inst.components.debuff:SetExtendedFn(Extended_timer)

    inst:AddComponent("timer")
    inst:ListenForEvent("timerdone", OnTimerDone)
end

local function Attached_notimer(inst, target, ...)
    inst.entity:SetParent(target.entity)
    inst.Transform:SetPosition(0, 0, 0) -- in case of loading
    inst:ListenForEvent("death", function(owner, data)
        inst.components.debuff:Stop()
    end, target)

    if inst._dd.fn_start ~= nil then
        inst._dd.fn_start(inst, target, ...)
    end
end
local function Detached_notimer(inst, target, ...)
    if inst._dd.fn_end ~= nil then
        inst._dd.fn_end(inst, target, ...)
    end
    inst:Remove()
end
local function Extended_notimer(inst, target, ...)
    if inst._dd.fn_again ~= nil then
        inst._dd.fn_again(inst, target, ...)
    end
end
local function InitNoTimerBuff(inst)
    inst.components.debuff:SetAttachedFn(Attached_notimer)
    inst.components.debuff:SetDetachedFn(Detached_notimer)
    inst.components.debuff:SetExtendedFn(Extended_notimer)
end

local function MakeBuff(data)
    table.insert(prefs, Prefab(
        data.name,
        function()
            local inst = CreateEntity()

            if data.addnetwork then -- 带有网络组件
                inst.entity:AddTransform()
                inst.entity:AddNetwork()
                -- inst.entity:Hide()
                inst.persists = false

                -- inst:AddTag("CLASSIFIED")
                inst:AddTag("NOCLICK")
                inst:AddTag("NOBLOCK")

                if data.fn_common ~= nil then
                    data.fn_common(inst)
                end

                inst.entity:SetPristine()
                if not TheWorld.ismastersim then return inst end
            else -- 无网络组件
                if not TheWorld.ismastersim then
                    -- Not meant for client!
                    inst:DoTaskInTime(0, inst.Remove)
                    return inst
                end
                inst.entity:AddTransform()
                -- Non-networked entity
                inst.entity:Hide()
                inst.persists = false
                inst:AddTag("CLASSIFIED")
            end

            inst._dd = data

            inst:AddComponent("debuff")
            inst.components.debuff.keepondespawn = true
            if data.notimer then
                InitNoTimerBuff(inst)
            else
                InitTimerBuff(inst)
            end

            if data.fn_server ~= nil then
                data.fn_server(inst)
            end

            return inst
        end,
        data.assets,
        data.prefabs
    ))
end

-----

local function BuffTalk_start(target, buff)
    if not target:HasTag("player") then
        return
    end
    target:PushEvent("foodbuffattached", { buff = "ANNOUNCE_ATTACH_" .. string.upper(buff.prefab), priority = 1 })
end
local function BuffTalk_end(target, buff)
    if not target:HasTag("player") then
        return
    end
    target:PushEvent("foodbuffdetached", { buff = "ANNOUNCE_DETACH_" .. string.upper(buff.prefab), priority = 1 })
end

local function IsAlive(inst)
    return inst.components.health ~= nil and not inst.components.health:IsDead() and not inst:HasTag("playerghost")
end

--------------------------------------------------------------------------
--[[ 锦上添花：攻击造成叠加的位面伤害 ]]
--------------------------------------------------------------------------

local function ClearCorolla(buff)
    buff.atk_lvl = 0
    buff.task_clear = nil
end
local function OnAttack_piggycorolla(doer, data, buff)
    if buff.atk_lvl < buff.atk_lvl_max then
        buff.atk_lvl = buff.atk_lvl + 1
    end
    if buff.task_clear ~= nil then
        buff.task_clear:Cancel()
    end
    buff.task_clear = buff:DoTaskInTime(5, ClearCorolla)

    if data and data.target and data.target:IsValid() then
        if IsAlive(data.target) then
            local atk = buff.atk_lvl * 3
            data.target:DoTaskInTime(0, function(target)
                if IsAlive(target) and target.components.combat ~= nil then
                    target.components.combat:GetAttacked(nil, atk, nil, nil, { planar = atk })
                end
            end)
        end
        if buff.atk_lvl >= buff.atk_lvl_max then
            local fx = SpawnPrefab("buff_pl_piggycorolla_fx")
            if fx ~= nil then
                fx.Transform:SetPosition(data.target.Transform:GetWorldPosition())
            end
        end
    end
end
local function UpdateCorollaMax(target, buff)
    if target.buff_pl_piggycorolla_up ~= nil then
        buff.atk_lvl_max = 5 + 1
        target.buff_pl_piggycorolla_up = nil
    end
end
local function OnSave_piggycorolla(inst, data)
    if inst.atk_lvl_max > 5 then
        data.atk_lvl_max = inst.atk_lvl_max - 5
    end
end
local function OnLoad_piggycorolla(inst, data)
    if data ~= nil and data.atk_lvl_max ~= nil and data.atk_lvl_max > 0 then
        inst.atk_lvl_max = 5 + data.atk_lvl_max
    end
end

--------------------------------------------------------------------------
--[[ 踏水 ]]
--------------------------------------------------------------------------

local function becameking(inst, target)
    if target.components.drownable ~= nil then
        if target.components.drownable.enabled == false then
            target.Physics:ClearCollisionMask()
            target.Physics:CollidesWith(COLLISION.GROUND)
            target.Physics:CollidesWith(COLLISION.OBSTACLES)
            target.Physics:CollidesWith(COLLISION.SMALLOBSTACLES)
            target.Physics:CollidesWith(COLLISION.CHARACTERS)
            target.Physics:CollidesWith(COLLISION.GIANTS)
        elseif target.components.drownable.enabled == true then
            if not target:HasTag("playerghost") then
                target.Physics:ClearCollisionMask()
                target.Physics:CollidesWith(COLLISION.WORLD)
                target.Physics:CollidesWith(COLLISION.OBSTACLES)
                target.Physics:CollidesWith(COLLISION.SMALLOBSTACLES)
                target.Physics:CollidesWith(COLLISION.CHARACTERS)
                target.Physics:CollidesWith(COLLISION.GIANTS)
            end
        end
    end
end

MakeBuff({
    name = "buff_pl_piggycorolla",
    -- assets = nil,
    -- prefabs = nil,
    -- notimer = nil,
    time_key = "time_l_piggycorolla",
    time_default = TUNING.SEG_TIME * 16, -- SEG_TIME = 30秒
    fn_start = function(buff, target)
        BuffTalk_start(target, buff)
        UpdateCorollaMax(target, buff)
        if target.components.combat ~= nil then
            buff.fn_onatk = function(target, data)
                OnAttack_piggycorolla(target, data, buff)
            end
            target:ListenForEvent("onattackother", buff.fn_onatk)
        end
    end,
    fn_again = function(buff, target)
        BuffTalk_start(target, buff)
        UpdateCorollaMax(target, buff)
    end,
    fn_end = function(buff, target)
        BuffTalk_end(target, buff)
        if buff.fn_onatk ~= nil then
            target:RemoveEventCallback("onattackother", buff.fn_onatk)
        end
    end,
    -- addnetwork = nil,fn_common = nil,
    fn_server = function(buff)
        buff.atk_lvl = 0
        buff.atk_lvl_max = 5
        -- buff.fn_onatk = nil
        buff.OnSave = OnSave_piggycorolla
        buff.OnLoad = OnLoad_piggycorolla -- 这个比OnAttached更早执行
    end
})
local MOD_NIGHTVISION_COLOURCUBES =
{
    day = "images/colour_cubes/caves_default.tex",
    dusk = "images/colour_cubes/caves_default.tex",
    night = "images/colour_cubes/caves_default.tex",
    full_moon = "images/colour_cubes/purple_moon_cc.tex",
}
MakeBuff({
    name = "buff_pl_nightvision",
    -- assets = nil,
    -- prefabs = nil,
    -- notimer = nil,
    time_key = "time_l_nightvision",
    time_default = TUNING.SEG_TIME * 16, -- SEG_TIME = 30秒

    addnetwork = true,
    fn_common = function(inst)
        inst:AddTag("CLASSIFIED")
        -- 用于同步buff的启用状态
        inst._enabled = net_bool(inst.GUID, "nightvision_buff._enabled", "enableddirty")
        local function buff_OnEnabledDirty(inst)
            if ThePlayer ~= nil and inst.entity:GetParent() == ThePlayer and ThePlayer.components.playervision ~= nil then
                if inst._enabled:value() then
                    ThePlayer.components.playervision:PushForcedNightVision(inst, 1, MOD_NIGHTVISION_COLOURCUBES,
                        true)
                else
                    ThePlayer.components.playervision:PopForcedNightVision(inst)
                end
            end
        end
        inst.entity:Hide()
        if not TheWorld.ismastersim then
            inst:ListenForEvent("enableddirty", buff_OnEnabledDirty)
            return inst
        end
    end,
    fn_start = function(inst, target)
        inst.entity:SetParent(target.entity)
        inst.Transform:SetPosition(0, 0, 0)

        -- 监听目标死亡事件，若目标死亡则停止buff
        inst:ListenForEvent("death", function()
            inst.components.debuff:Stop()
        end, target)

        -- 给目标添加夜视效果
        if target.components.playervision ~= nil then
            target.components.playervision:PushForcedNightVision(inst, 1, MOD_NIGHTVISION_COLOURCUBES, true)
            inst._enabled:set(true)
        end
    end,
    fn_again = function(inst, target)
        inst.entity:SetParent(target.entity)
        inst.Transform:SetPosition(0, 0, 0)

        -- 监听目标死亡事件，若目标死亡则停止buff
        inst:ListenForEvent("death", function()
            inst.components.debuff:Stop()
        end, target)

        -- 给目标添加夜视效果
        if target.components.playervision ~= nil then
            target.components.playervision:PushForcedNightVision(inst, 1, MOD_NIGHTVISION_COLOURCUBES, true)
            inst._enabled:set(true)
        end
    end,
    fn_end = function(inst, target)
        if target ~= nil and target:IsValid() then
            -- 移除目标的夜视效果
            if target.components.playervision ~= nil then
                target.components.playervision:PopForcedNightVision(inst)
                inst._enabled:set(false)
            end
        end
    end,
})

MakeBuff({
    name = "buff_pl_waterwalking",
    time_key = "time_l_waterwalking",
    time_default = TUNING.SEG_TIME * 16, -- SEG_TIME = 30秒
    fn_start = function(buff, target)
        if target.components.drownable then
            target.components.drownable.enabled = false
        end
        if target.components.talker then
            target.components.talker:Say(TYMG_MOD.SEA_SWALLOWBUFFSTART)
        end

        target.task_buff_pl_waterwalking = target:DoPeriodicTask(0.1, function()
            if not target:HasTag("playerghost") then
                target.Physics:ClearCollisionMask()
                target.Physics:CollidesWith(COLLISION.GROUND)
                target.Physics:CollidesWith(COLLISION.OBSTACLES)
                target.Physics:CollidesWith(COLLISION.SMALLOBSTACLES)
                target.Physics:CollidesWith(COLLISION.CHARACTERS)
                target.Physics:CollidesWith(COLLISION.GIANTS)
                local is_moving = target.sg:HasStateTag("moving")
                local is_running = target.sg:HasStateTag("running")
                if target.components.drownable and target.components.drownable:IsOverWater() then
                    if is_running or is_moving then
                        target.delay_count = (target.delay_count or 0) + 1
                        if target.delay_count >= 2 then
                            SpawnPrefab("weregoose_splash_less" .. tostring(math.random(2))).entity:SetParent(target
                                .entity)
                            target.delay_count = 0
                        end
                    end
                end
            end
            if buff.components.timer and buff.components.timer:TimerExists("buffover") and not buff.needsay then
                if buff.components.timer:GetTimeLeft("buffover") < 20 then
                    buff.needsay = true
                    if target.components.talker then
                        target.components.talker:Say(TYMG_MOD.SEA_SWALLOWBUFFWILLEND)
                    end
                end
            end
        end)
    end,
    fn_again = function(buff, target)
        if target.components.drownable then
            target.components.drownable.enabled = false
        end
        if target.components.talker then
            target.components.talker:Say(TYMG_MOD.SEA_SWALLOWBUFFSTART)
        end

        target.task_buff_pl_waterwalking = target:DoPeriodicTask(0.1, function()
            if not target:HasTag("playerghost") then
                target.Physics:ClearCollisionMask()
                target.Physics:CollidesWith(COLLISION.GROUND)
                target.Physics:CollidesWith(COLLISION.OBSTACLES)
                target.Physics:CollidesWith(COLLISION.SMALLOBSTACLES)
                target.Physics:CollidesWith(COLLISION.CHARACTERS)
                target.Physics:CollidesWith(COLLISION.GIANTS)
                local is_moving = target.sg:HasStateTag("moving")
                local is_running = target.sg:HasStateTag("running")
                if target.components.drownable and target.components.drownable:IsOverWater() then
                    if is_running or is_moving then
                        target.delay_count = (target.delay_count or 0) + 1
                        if target.delay_count >= 2 then
                            SpawnPrefab("weregoose_splash_less" .. tostring(math.random(2))).entity:SetParent(target
                                .entity)
                            target.delay_count = 0
                        end
                    end
                end
            end
            if buff.components.timer and buff.components.timer:TimerExists("buffover") and not buff.needsay then
                if buff.components.timer:GetTimeLeft("buffover") < 20 then
                    buff.needsay = true
                    if target.components.talker then
                        target.components.talker:Say(TYMG_MOD.SEA_SWALLOWBUFFWILLEND)
                    end
                end
            end
        end)
    end,
    fn_end = function(buff, target)
        if target.task_buff_pl_waterwalking then
            target.task_buff_pl_waterwalking:Cancel()
            target.task_buff_pl_waterwalking = nil
        end
        if target.components.drownable.enabled == false then
            target.components.drownable.enabled = true
        end
        if target.components.talker then
            target.components.talker:Say(TYMG_MOD.SEA_SWALLOWBUFFEND)
        end
        if not target:HasTag("playerghost") then
            target:DoTaskInTime(0.5, function(inst)
                print("buff_pl_waterwalking")
                target.Physics:ClearCollisionMask()
                target.Physics:CollidesWith(COLLISION.WORLD)
                target.Physics:CollidesWith(COLLISION.OBSTACLES)
                target.Physics:CollidesWith(COLLISION.SMALLOBSTACLES)
                target.Physics:CollidesWith(COLLISION.CHARACTERS)
                target.Physics:CollidesWith(COLLISION.GIANTS)
            end)
        end
    end,
})
MakeBuff({
    name = "buff_pl_deliciouschowder",
    time_key = "time_l_deliciouschowder",
    time_default = TUNING.SEG_TIME * 16, -- SEG_TIME = 30秒
    fn_start = function(buff, target)
        if target.components.eater then
            if not target:HasTag("ignoresspoilage") then
                target.components.eater:SetIgnoresSpoilage(true)
            else
                -- 兼容其他mod
                target.components.eater.noneedtag_ignoresspoilage = true
            end
            if target:HasTag("nospoiledfood") then
                target.components.eater:SetRefusesSpoiledFood()
            else
                -- 兼容其他mod
                target.components.eater.noneedtag_nospoiledfood = true
            end

            target.components.eater.caneat_deliciouschowder = target.components.eater.caneat
            target.components.eater.preferseating_deliciouschowder = target.components.eater.preferseating
            target.components.eater:SetDiet({ FOODGROUP.OMNI }, { FOODGROUP.OMNI })
            if target.components.eater.preferseatingtags then
                target.components.eater.preferseatingtags_deliciouschowder = {}
                for key, value in pairs(target.components.eater.preferseatingtags) do
                    target.components.eater.preferseatingtags_deliciouschowder[key] = value
                end
                target.components.eater.preferseatingtags = nil
            end
        end
    end,
    fn_again = function(buff, target)
        if target.components.eater then
            if not target:HasTag("ignoresspoilage") then
                target.components.eater:SetIgnoresSpoilage(true)
            else
                -- 兼容其他mod
                target.components.eater.noneedtag_ignoresspoilage = true
            end
            if target:HasTag("nospoiledfood") then
                target.components.eater:SetRefusesSpoiledFood()
            else
                -- 兼容其他mod
                target.components.eater.noneedtag_nospoiledfood = true
            end

            target.components.eater.caneat_deliciouschowder = target.components.eater.caneat
            target.components.eater.preferseating_deliciouschowder = target.components.eater.preferseating
            target.components.eater:SetDiet({ FOODGROUP.OMNI }, { FOODGROUP.OMNI })
            if target.components.eater.preferseatingtags then
                target.components.eater.preferseatingtags_deliciouschowder = {}
                for key, value in pairs(target.components.eater.preferseatingtags) do
                    target.components.eater.preferseatingtags_deliciouschowder[key] = value
                end
                target.components.eater.preferseatingtags = nil
            end
        end
    end,
    fn_end = function(buff, target)
        if target.components.eater then
            if target:HasTag("ignoresspoilage") and not target.components.eater.noneedtag_ignoresspoilage then
                target.components.eater:SetIgnoresSpoilage()
            end
            if not target:HasTag("nospoiledfood") and not target.components.eater.noneedtag_nospoiledfood then
                target.components.eater:SetRefusesSpoiledFood(true)
            end

            if target.components.eater.caneat_deliciouschowder then
                target.components.eater:SetDiet(target.components.eater.caneat_deliciouschowder,
                    target.components.eater.preferseating_deliciouschowder)
            end
            if target.components.eater.preferseatingtags_deliciouschowder then
                target.components.eater.preferseatingtags = target.components.eater.preferseatingtags_deliciouschowder
            end
        end
    end,
})

------------
--荔只只物语
------------

if not SETS_PASTORAL.ENABLEDMODS["lz"] then
    MakeBuff({
        name = "lz_taohuazui_buff",
        time_key = "lz_taohuazui_buff",
        time_default = 240,
        fn_start = function(inst, target)
            if target and target.components.drownable and target.sg then
                target.components.drownable.enabled = false
                becameking(inst, target)
                inst.delay_count = 0
                inst.lz_moving_task = inst:DoPeriodicTask(0.1, function()
                    local is_moving = target.sg:HasStateTag("moving") --在移动
                    local is_running = target.sg:HasStateTag("running") --在跑步
                    if target.components.drownable ~= nil and target.components.drownable:IsOverWater() then
                        if is_running or is_moving then
                            inst.delay_count = inst.delay_count + 1
                            if inst.delay_count >= 1.5 then
                                SpawnPrefab("weregoose_splash_less" .. tostring(math.random(2))).entity:SetParent(target.entity)
                                inst.delay_count = 0
                            end
                        end
                    end
                end)
            end
        end,
        fn_end = function(inst, target)
            if inst.lz_moving_task then
                inst.lz_moving_task:Cancel()
                inst.lz_moving_task = nil
            end
            if target and target.components.drownable then
                target.components.drownable.enabled = true
                becameking(inst, target)
            end
        end,
    })
end
--------------------
--------------------

return unpack(prefs)
